#include <buola/gui.h>

#include <buola/image/cfont.h>
#include <buola/image/cgc.h>
#include <buola/gui/cicon.h>
#include <buola/gui/cconfig.h>

#include <buola/gui/caccelerator.h>

#include <buola/widgets/ctabbox.h>
#include <buola/widgets/ctheme.h>

namespace buola { namespace gui {

CTabBox::CTabBox()
{
    SetStyle(TABBOX_TEXT|TABBOX_TOP);
    mIconSize=CIcon::MEDIUM;

    mNextID=0x10000000;

    mSelPos=-1;

    mIsPosValid=false;

    get_default_theme()->PrepareTabBox(this);

    mFont=img::CFont::GetStock(L"control");
    SetPointer(CPointer::STOCK_DEFAULT);

    mCtrlTabAccel.reset(new CAccelerator(KEY_TAB,KEYMOD_CONTROL,CAccelerator::LOCAL,this));
    mCtrlTabAccel->eTriggered.Connect(&CTabBox::OnCtrlTab,this);
}

CTabBox::~CTabBox()
{
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////// Interface //////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

int CTabBox::GetItemPos(int pID)
{
    int lCount=mItems.size();
    for(int i=0;i<lCount;i++)
        if(mItems[i]->mID==pID)
            return i;
    return -1;
}

CTabBoxItem *CTabBox::AddItem(const std::wstring &pName,PIcon pIcon,PWindow pWindow,int pID)
{
    CTabBoxItem *lNewItem=new CTabBoxItem;

    if(!pWindow)
        pWindow=new CWindow;

    Place(pWindow);
    pWindow->MoveResize(mCanvasRect);
    pWindow->Hide();

    if(GetStyle(TABBOX_MENULIKE))
        pWindow->SetBackColor(img::rgb8(224,224,224));

    lNewItem->mID=(pID>=0)?pID:mNextID++;
    lNewItem->mText=pName;
    lNewItem->mIcon=pIcon;
    lNewItem->mCanvas=pWindow;

    mItems.push_back(lNewItem);

    if(mItems.size()==1)
    {
        SelectItemPos(0);
    }

    mIsPosValid=false;
    Refresh();

    return lNewItem;
}

CTabBoxItem *CTabBox::FindItem(const std::wstring &pText)
{
    int lCount=mItems.size();
    for(int i=0;i<lCount;i++)
        if(mItems[i]->mText==pText)
            return mItems[i];
    return nullptr;
}

CTabBoxItem *CTabBox::GetItem(int pID)
{
    int lPos=GetItemPos(pID);
    if(lPos>=0) return mItems[lPos];
    return nullptr;
}

bool CTabBox::RemoveItem(int pID)
{
    int lPos=GetItemPos(pID);
    if(lPos<0) return false;
    delete mItems[lPos];
    mItems.erase(mItems.begin()+lPos);

    mIsPosValid=false;

    if(mSelPos==lPos)
    {
        mSelPos=-1;

        if(mItems.size())
        {
            if(lPos>=mItems.size())
                lPos--;
            SelectItemPos(lPos);
        }
        else
        {
            Refresh();
        }
    }
    else
    {
        if(mSelPos>lPos)
            mSelPos--;
        Refresh();
    }

    return true;
}

void CTabBox::RemoveAll()
{
    for(int i=0;i<mItems.size();i++)
    {
        mItems[i]->mCanvas->Hide();
        delete mItems[i];
    }

    mItems.clear();

    mIsPosValid=false;
    Refresh();
}

std::wstring CTabBox::GetItemText(int pID)
{
    int lPos=GetItemPos(pID);
    if(lPos>=0)
        return mItems[lPos]->mText;
    return(std::wstring());
}

void CTabBox::SetItemText(int pID,const std::wstring &pText)
{
    int lPos=GetItemPos(pID);
    if(lPos>=0)
    {
        if(mItems[lPos]->mText==pText) return;
        mItems[lPos]->mText=pText;
        mIsPosValid=false;
        Refresh();
    }
}

void CTabBox::SelectItemPos(int pItem)
{
    if(mSelPos==pItem) return;

    if(GetStyle(TABBOX_MENULIKE))
        mItems[pItem]->mCanvas->SetBackColor(img::rgb8(224,224,224));
    mItems[pItem]->mCanvas->Show();

    if(mSelPos>=0)
    {
        mItems[mSelPos]->mCanvas->Hide();
    }

    mSelPos=pItem;

    mItems[pItem]->mCanvas->SetFocusToChild();

    eActivated(mItems[pItem],mItems[pItem]->mID);

    Refresh();
}

void CTabBox::SelectItem(int pID)
{
    SelectItemPos(GetItemPos(pID));
}

int CTabBox::GetSelItem()
{
    if(mSelPos>=0)
        return mItems[mSelPos]->mID;
    return -1;
}

void CTabBox::SetIconSize(int pSize)
{
    if(pSize!=mIconSize)
    {
        mIconSize=pSize;

        mIsPosValid=false;
        Refresh();
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///////////////////// Internal functions //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

bool CTabBox::HitTestItem(int i,const CPoint_d &pP)
{
    CTabBoxItem *lI=mItems[i];

    return (CRect_d(lI->mPos,lI->mSize).Contains(pP));
}

//!sets the size of the item, and the y coordinate of the position
void CTabBox::ShapeItem(int i)
{
    CTabBoxItem *lI=mItems[i];

    if(GetStyle(TABBOX_TOP))
    {
        lI->mPos.y=0;
        lI->mSize.x=mFont->GetWidth(lI->mText)+18;
        lI->mSize.y=mCanvasRect.t;
    }
    else
    {
        lI->mPos.y=mCanvasRect.b+1;
        lI->mSize.x=mFont->GetWidth(lI->mText)+18;
        lI->mSize.y=Size().y-mCanvasRect.b;
    }

    if(GetStyle(TABBOX_PREFERENCES))
    {
        lI->mSize.x-=4;
        if(lI->mIcon)
            lI->mSize.x=max(lI->mSize.x,(double)mIconSize+8);
    }
    else
    {
        if(GetStyle(TABBOX_UNDERTB))
            lI->mSize.x+=12;

        if(lI->mIcon)
            lI->mSize.x+=mIconSize;
    }

    lI->mCanvas->MoveResize(mCanvasRect);

    lI->mPoints=false;
}

void CTabBox::AdjustItem(int i,double pWidth)
{
    CTabBoxItem *lI=mItems[i];

    if(GetStyle(TABBOX_UNDERTB))
    {
        if(mFont->GetWidth(lI->mText)<=pWidth-30)
            return;

        lI->mPoints=true;
        lI->mVisibleText=lI->mText.length()-1;

        while(lI->mVisibleText>=1&&mFont->GetWidth(lI->mText.substr(0,
                        lI->mVisibleText)+L"...")>pWidth-30)
            lI->mVisibleText--;
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CTabBox::OnPreExpose()
{
    if(mIsPosValid) return;

    mHidden=(GetStyle(TABBOX_HIDEIFONE)&&mItems.size()<=1)||
                  (GetStyle(TABBOX_HIDEIFZERO)&&mItems.size()==0);

    if(mHidden)
    {
        mCanvasRect={0,0,Size().x,Size().y};
    }
    else if(GetStyle(TABBOX_PREFERENCES))
    {
        mCanvasRect={0,56,Size().x,Size().y};
    }
    else if(GetStyle(TABBOX_TOP))
    {
        mCanvasRect=CRect_d(0,mIconSize+3,Size().x,Size().y);
    }
    else
    {
        mCanvasRect=CRect_d(0,0,Size().x,Size().y-mIconSize-3);
    }

    if(GetStyle(TABBOX_MENULIKE))
    {
        mCanvasRect.l+=20;
        mCanvasRect.t+=10;
        mCanvasRect.r-=20;
        mCanvasRect.b-=20;
    }
    else
    {
        mCanvasRect.l+=1;
        mCanvasRect.t+=1;
        mCanvasRect.r-=1;
        mCanvasRect.b-=1;
    }

    mTextSize.y=mFont->GetHeight();

    if(!mItems.size())
    {
        mFirstVisible=0;
        mLastVisible=-1;
        return;
    }

    for(int i=0;i<mItems.size();i++)
        ShapeItem(i);

    double lW=0.0;
    for(int i=0;i<mItems.size();i++)
        lW+=mItems[i]->mSize.x;

    double lX=0.0;

    if(GetStyle(TABBOX_UNDERTB))
    {
        if(lW>Size().x-20) //it doesn't fit
        {
            int lNumItems=mItems.size();

            double lMaxWidth=(Size().x-20)/lNumItems;

            if(lMaxWidth<80)
            {
                lNumItems=(int)(Size().x-20)/80.0;
                lMaxWidth=(Size().x-20)/lNumItems;

                if(mFirstVisible+lNumItems>mItems.size())
                {
                    mFirstVisible=mItems.size()-lNumItems;
                }
                mLastVisible=mFirstVisible+lNumItems-1;
            }
            else
            {
                mFirstVisible=0;
                mLastVisible=mItems.size()-1;
            }

            int lCount=0; //count how many are larger than max width
            double lWShorter=0.0;
            for(int i=mFirstVisible;i<=mLastVisible;i++)
            {
                if(mItems[i]->mSize.x>lMaxWidth)
                {
                    lCount++;
                }
                else
                {
                    lWShorter+=mItems[i]->mSize.x;
                }
            }

            double lAvgWidth=floor((Size().x-20-lWShorter)/lCount);

            double lNewW=0;

            for(int i=mFirstVisible;i<=mLastVisible;i++)
            {
                if(mItems[i]->mSize.x>lAvgWidth)
                {
                    mItems[i]->mSize.x=lAvgWidth;
                }

                lNewW+=mItems[i]->mSize.x;
            }

            //now, add a bit to every tab so that they fill the whole space
            double lFill=(Size().x-20-lNewW)/lNumItems;

            for(int i=mFirstVisible;i<=mLastVisible;i++)
            {
                mItems[i]->mSize.x+=lFill;
                AdjustItem(i,mItems[i]->mSize.x);
            }
        }
        else
        {
            mFirstVisible=0;
            mLastVisible=mItems.size()-1;

        }

        lX=10;
    }
    else if(GetStyle(TABBOX_PREFERENCES))
    {
        lX=6;
        mFirstVisible=0;
        mLastVisible=mItems.size()-1;
    }
    else
    {
        lX=floor((Size().x-lW)/2);
        mFirstVisible=0;
        mLastVisible=mItems.size()-1;
    }

    for(int i=mFirstVisible;i<=mLastVisible;i++)
    {
        mItems[i]->mPos.x=lX;
        lX+=mItems[i]->mSize.x;
    }

    mIsPosValid=true;
}

void CTabBox::OnExpose(const CExposeEvent &pE)
{
    img::CGC *lGC=pE.mGC;

    lGC->SetSource(gConfig.mColors.Get(L"normal_text"));
    if(GetStyle(TABBOX_MENULIKE))
    {
        CRect_d lRect(mCanvasRect.l-5,floor((mCanvasRect.t)/2),
                    mCanvasRect.r+5,mCanvasRect.b+5);

        lGC->SetSource(img::rgb8(224,224,224));

        lGC->Path().MoveTo(CPoint_d(lRect.r-7,lRect.t));
        lGC->Path().LineTo(CPoint_d(lRect.r-5,lRect.t));
        lGC->Path().Arc(CPoint_d(lRect.r-5,lRect.t+5),5,-M_PI/2,0);
        lGC->Path().LineTo(CPoint_d(lRect.r,lRect.b-5));
        lGC->Path().Arc(CPoint_d(lRect.r-5,lRect.b-5),5,0,M_PI/2);
        lGC->Path().LineTo(CPoint_d(lRect.r-7,lRect.b));
        lGC->Path().Close();
        lGC->FillPath();

        lGC->Path().MoveTo(CPoint_d(lRect.r,lRect.b-7));
        lGC->Path().LineTo(CPoint_d(lRect.r,lRect.b-5));
        lGC->Path().Arc(CPoint_d(lRect.r-5,lRect.b-5),5,0,M_PI/2);
        lGC->Path().LineTo(CPoint_d(lRect.l+5,lRect.b));
        lGC->Path().Arc(CPoint_d(lRect.l+5,lRect.b-5),5,M_PI/2,M_PI);
        lGC->Path().LineTo(CPoint_d(lRect.l,lRect.b-7));
        lGC->Path().Close();
        lGC->FillPath();

        lGC->Path().MoveTo(CPoint_d(lRect.l+7,lRect.b));
        lGC->Path().LineTo(CPoint_d(lRect.l+5,lRect.b));
        lGC->Path().Arc(CPoint_d(lRect.l+5,lRect.b-5),5,M_PI/2,M_PI);
        lGC->Path().LineTo(CPoint_d(lRect.l,lRect.t+5));
        lGC->Path().Arc(CPoint_d(lRect.l+5,lRect.t+5),5,M_PI,-M_PI/2);
        lGC->Path().LineTo(CPoint_d(lRect.l+7,lRect.t));
        lGC->Path().Close();
        lGC->FillPath();

        lGC->Path().MoveTo(CPoint_d(lRect.l,lRect.t+22));
        lGC->Path().LineTo(CPoint_d(lRect.l,lRect.t+5));
        lGC->Path().Arc(CPoint_d(lRect.l+5,lRect.t+5),5,M_PI,-M_PI/2);
        lGC->Path().LineTo(CPoint_d(lRect.r-5,lRect.t));
        lGC->Path().Arc(CPoint_d(lRect.r-5,lRect.t+5),5,-M_PI/2,0);
        lGC->Path().LineTo(CPoint_d(lRect.r,lRect.t+22));
        lGC->Path().Close();
        lGC->FillPath();

        lRect.t+=1.5;
        lRect.l-=0.5;
        lRect.r+=0.5;
        lRect.b+=0.5;

        lGC->SetLineWidth(1.0);
        lGC->Path().MoveTo(CPoint_d(lRect.l+5,lRect.t));
        lGC->Path().LineTo(CPoint_d(lRect.r-5,lRect.t));
        lGC->Path().Arc(CPoint_d(lRect.r-5,lRect.t+5),5,-M_PI/2,0);
        lGC->Path().LineTo(CPoint_d(lRect.r,lRect.b-5));
        lGC->Path().Arc(CPoint_d(lRect.r-5,lRect.b-5),5,0,M_PI/2);
        lGC->Path().LineTo(CPoint_d(lRect.l+5,lRect.b));
        lGC->Path().Arc(CPoint_d(lRect.l+5,lRect.b-5),5,M_PI/2,M_PI);
        lGC->Path().LineTo(CPoint_d(lRect.l,lRect.t+5));
        lGC->Path().Arc(CPoint_d(lRect.l+5,lRect.t+5),5,M_PI,-M_PI/2);
//      lGC->SetSource(RGB(170,170,170));
//      lGC->FillPath(true);
        lGC->SetSource(img::rgb8(195,195,195));
        lGC->StrokePath();
        lGC->SetSource(img::rgb8(168,168,168));
        lGC->Path().Reset();
        lGC->Path().Arc(CPoint_d(lRect.l+6,lRect.t+6),7,-3*(M_PI/4),-M_PI/2);
        lGC->Path().MoveTo(CPoint_d(lRect.l+6,lRect.t-1.0));
        lGC->Path().LineTo(CPoint_d(lRect.r-6,lRect.t-1.0));
        lGC->Path().Arc(CPoint_d(lRect.r-6,lRect.t+6),7,-M_PI/2,-M_PI/4);
        lGC->StrokePath();
        lGC->Path().MoveTo(CPoint_d(0,0.5));
        lGC->Path().LineTo(CPoint_d(Size().x,0.5));
        lGC->Path().MoveTo(CPoint_d(0,Size().y-0.5));
        lGC->Path().LineTo(CPoint_d(Size().x,Size().y-0.5));
        lGC->SetSource(img::rgb8(64,64,64));
        lGC->StrokePath();
    }
    else
    {
        lGC->Path().Rect(CPoint_d(mCanvasRect.l-1,mCanvasRect.t-1),
                        CSize_d(mCanvasRect.r+1,mCanvasRect.b+1));
        lGC->StrokePath();
    }

    if(mHidden) return;

    CTheme::EDrawStyle lFlags=CTheme::DRAW_NONE;

    if(mFirstVisible>0)
        lFlags|=CTheme::DRAW_LEFTARROW;
    if(mLastVisible<mItems.size()-1)
        lFlags|=CTheme::DRAW_RIGHTARROW;

    if(GetStyle(TABBOX_TOP))
    {
        get_default_theme()->DrawTabBox(lGC,CRect_d(0,0,Size().x,mCanvasRect.t-1),this,lFlags);
    }
    else
    {
        get_default_theme()->DrawTabBox(lGC,CRect_d(0,mCanvasRect.b+1,Size().x,Size().y),this,lFlags);
    }

    for(int i=mFirstVisible;i<=mLastVisible;i++)
    {
        CTheme::EDrawStyle lFlags=CTheme::DRAW_NONE;
        if(i==0) lFlags|=CTheme::DRAW_LEFT;
        if(i==mItems.size()-1) lFlags|=CTheme::DRAW_RIGHT;
        if(i==mSelPos)
            lFlags|=CTheme::DRAW_SELECTED;
        get_default_theme()->DrawTabBoxItem(lGC,CRect_d(mItems[i]->mPos,mItems[i]->mSize),
                        this,mItems[i],lFlags);
    }
}

void CTabBox::OnButtonDown(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
        for(int i=mFirstVisible;i<=mLastVisible;i++)
        {
            if(HitTestItem(i,pE.mPoint))
            {
                if(GetStyle(TABBOX_UNDERTB))
                {
                    CPoint_d lCenter(mItems[i]->mPos.x+mItems[i]->mSize.x-12,
                            mItems[i]->mPos.y+21);
                    if(dist(lCenter,pE.mPoint)<8)
                    {
                        eCloseItem(mItems[i],mItems[i]->mID);
                        return;
                    }
                }
                SelectItemPos(i);
                return;
            }
        }

        if(mFirstVisible>0&&pE.mPoint.x<10)
        {
            mFirstVisible--;
            mIsPosValid=false;
            Refresh();
        }
        else if(mLastVisible<mItems.size()-1&&pE.mPoint.x>Size().x-10)
        {
            mFirstVisible++;
            mIsPosValid=false;
            Refresh();
        }
    }
}

void CTabBox::OnButtonUp(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
    }
}

void CTabBox::OnMouseMove(const CMouseEvent &pE)
{
}

void CTabBox::OnSize(const CGeometryEvent &pE)
{
    mIsPosValid=false;
}

void CTabBox::OnStyleChanged(TStyle pOld)
{
    int lChange=pOld^GetStyle();

    if(lChange&STYLE_FAKE)
    {
        if(GetStyle(STYLE_FAKE))
        {
            SetInputMode(EInputMode::MOUSETRANSPARENT);
        }
        else
        {
            SetInputMode(EInputMode::NORMAL);
        }
    }

    get_default_theme()->PrepareTabBox(this);
    mIsPosValid=false;
    Refresh();
}

void CTabBox::OnCtrlTab()
{
    if(mSelPos>=0)
    {
        if(mSelPos<mItems.size()-1)
            SelectItemPos(mSelPos+1);
        else
            SelectItemPos(0);
    }
}

/*namespace gui*/ } /*namespace buola*/ }
